package MT::Test;
use strict;

use vars qw( $VERSION );
$VERSION = '0.2';

use base 'Test::Builder::Module';
use vars qw(@EXPORT);
@EXPORT =
  qw( ok is isnt like unlike cmp_ok diag use_ok require_ok run_test_tmpl
  has_plugin has_plugin_enabled );

my $FMGR;

sub run_test_tmpl {
    my ($tmpl, $begin, $end) = @_;
    my $tb = MT::Test->builder;
    require MT::Callback;
    $begin = MT::Callback->new({code => $begin}) if $begin;
    $end   = MT::Callback->new({code => $end})   if $end;
    if (ref $tmpl eq 'SCALAR') {
        $tmpl = $$tmpl;
    } elsif (ref $tmpl eq 'CODE') {
        $tmpl = $tmpl->();
    } elsif (defined $tmpl) {
        require File::Spec;
        $tmpl = File::Spec->catfile(File::Spec->curdir, 't', 'tmpl', $tmpl)
          unless (File::Spec->file_name_is_absolute($tmpl));
        require MT::FileMgr;
        $FMGR ||= MT::FileMgr->new('Local');
        $tmpl = $FMGR->get_data($tmpl) or return $tb->diag($FMGR->errstr);
    } else {
        return $tb->diag("$tmpl is not a legal or recognized parameter.");
    }
    require MT::Template::Context;
    my $ctx = MT::Template::Context->new;
    require MT::Builder;
    my $builder = MT::Builder->new;
    $ctx->stash('builder', $builder);
    my $tokens = $builder->compile($ctx, $tmpl)
      or return $tb->diag($builder->errstr);
    if ($begin) {
        $begin->invoke($ctx) or return $tb->diag($begin->errstr);
    }
    my $out = $builder->build($ctx, $tokens);
    return $tb->diag($builder->errstr) unless defined $out;
    if ($end) {
        $end->invoke($ctx) or return $tb->diag($end->errstr);
    }
    1;
}

sub has_plugin ($;$) {
    my $tb = MT::Test->builder;
    $tb->ok(defined $MT::Plugins{$_[0]}, 'has_plugin ' . $_[0]);
}

sub has_plugin_enabled ($;$) {
    my $tb     = MT::Test->builder;
    my $result =
      $tb->ok($MT::Plugins{$_[0]}{enabled}, 'has_plugin_enabled ' . $_[0]);
    $tb->diag($MT::Plugins{$_[0]}{error}) if $MT::Plugins{$_[0]}{error};
    $result;
}

#--- Test::More emulation

sub ok ($;$)      { MT::Test->builder->ok(@_); }
sub is ($$;$)     { MT::Test->builder->is_eq(@_) }
sub isnt ($$;$)   { MT::Test->builder->isnt_eq(@_) }
sub like ($$;$)   { MT::Test->builder->like(@_) }
sub unlike ($$;$) { MT::Test->builder->unlike(@_) }
sub cmp_ok($$$;$) { MT::Test->builder->cmp_ok(@_) }
sub diag          { MT::Test->builder->diag(@_) }
sub plan          { MT::Test->builder->plan(@_) }

sub use_ok ($;@) {
    my ($module, @imports) = @_;
    @imports = () unless @imports;
    my $tb = MT::Test->builder;
    my ($pack, $filename, $line) = caller;
    local ($@, $!);    # eval sometimes interferes with $!
    if (@imports == 1 and $imports[0] =~ /^\d+(?:\.\d+)?$/) {

        # probably a version check.  Perl needs to see the bare number
        # for it to work with non-Exporter based modules.
        eval <<USE;
package $pack;
use $module $imports[0];
USE
    } else {
        eval <<USE;
package $pack;
use $module \@imports;
USE
    }
    my $ok = $tb->ok(!$@, "use $module;");
    unless ($ok) {
        chomp $@;
        $@ =~ s{^BEGIN failed--compilation aborted at .*$}
                {BEGIN failed--compilation aborted at $filename line $line.}m;
        $tb->diag(<<DIAGNOSTIC);
    Tried to use '$module'.
    Error:  $@
DIAGNOSTIC
    }
    $ok;
}

sub require_ok ($) {
    my ($module) = shift;
    my $tb       = MT::Test->builder;
    my $pack     = caller;
    $module = qq['$module'] unless _is_module_name($module);   # module or file?
    local ($!, $@);    # eval sometimes interferes with $!
    eval <<REQUIRE;
package $pack;
require $module;
REQUIRE
    my $ok = $tb->ok(!$@, "require $module;");
    unless ($ok) {
        chomp $@;
        $tb->diag(<<DIAGNOSTIC);
    Tried to require '$module'.
    Error:  $@
DIAGNOSTIC
    }
    $ok;
}

sub _is_module_name {
    my $module = shift;
    $module =~ s/\b::\b//g;
    $module =~ /^[a-zA-Z]\w*$/;
}

1;

__END__

=head1 NAME

MT::Test - a framework for testing Movable Type plugins.

=head1 DESCRIPTION

MT::Test is a framework for testing Movable Type plugins. It
is a subclass of Test::Builder::Module from Test::More that
adds additional MT specific tests and functionality.

Those familiar with Test::More will be right at home. The one
major addition is the ability to test template tag output 
using one method and half a dozen testing specific template 
tags.

The framework includes a command tool for running the tests
with specific configuration or even with a fresh default database. See 
the manpage for C<mtt>.

=head1 METHODS

=over

=item ok 

=item is 

=item isnt 

=item like 

=item unlike 

=item cmp_ok 

=item diag 

These methods are inherited from Test::More. See the
Test::More manpages for more.

=item use_ok 

=item require_ok 

These methods are essentially the same found in Test::More
however the MT system libraries such as C<extlib> are
also considered.

=item run_test_tmpl( $tmpl, [\&begin, \&end]);

This method takes a template and performs the embedded tests
on its output. These tests are represented using one of the
special test operation tags providing by this framework.
(See TAGS).

A string parameter containing the template markup for the
test is required. You can optionally pass references a
reference to code to run before and after the template test.
These methods are useful for initializing the context of
doing follow-up test on the final context after a template
run.
      
=item has_plugin($plugin_sig)

This method will test the MT system for the existence of a
specific plugin and will fail if it is not found. A plugin
signature string such as 'plugins/feeds/mt-feeds.pl' is
required.

=item has_plugin_enabled($plugin_sig)

This method will test if a specific plugin is enabled in 
the system. It will fail if it is disabled or not found. A plugin
signature string such as 'plugins/feeds/mt-feeds.pl' is
required.

=head1 TAGS

=over

=item MTTestIs var="" stash="" val="" test_name="" blank="1"

This tag tests if the output of its contents matches a
specific value. The value to test for can come from a
template variable using the C<var> attribute, the stash
using the C<stash> attribute or from the tag's C<val>
attribute. In order to test if the contents is blank (an
empty string) pass a true value to the C<blank> attribute.
One and only one of these attributes must be passed.

Optionally the C<test_name> attribute can be used to pass a
string identify the test when run.

=item MTTestIsNot var="" stash="" val="" test_name="" blank="1"

This tag tests if the output of its contents does NOT match a
specific value. It is the opposite of C<MTTestIs> and takes the 
same attributes.

=item MTTestCompareOK var="" stash="" val="" test_name="" blank="1" op=""

This tag tests if the output of its contents does NOT match a
specific value. 

The C<op> attribute is required attribute of <MTTestCompareOK> and 
must equal one of Perl's standard string comparison operators:

    eq      equals
    ne      not equal
    lt      less than 
    gt      greater than
    le      less than equal
    ge      greater then or equal

Using C<op='eq'> is the equivalent of C<MTTestIs> and
C<op='ne'> is the equivalent of <MTTestIsNot>.

Optionally the C<test_name> attribute can be used to pass a
string identify the test when run.

=item MTTestLike pattern="" test_name=""

This tag will test if the contained output matches the
regular expression passed in using the required C<pattern> attribute.
C<test_name> is optional, but recommend for better feedback.

=item MTTestUnlike pattern="" test_name=""

This tag will test if the contained output does not match the
regular expression passed in using the required C<pattern> attribute.
C<test_name> is optional, but recommend for better feedback.

=back

=head1 REQUIREMENTS

MT::Test requires at least Movable Type 3.3 or Movable Type
Enterprise 1.02. MT::Test also requires C<MT::Tool> and
C<Test::Simple>.


=head1 KNOWN ISSUE

I'm not entire statisfied with one common occurance when
using C<run_test_tmpl>.

The test count is not incremented properly if a test
template has errors or in some cases when a condition causes
a test not to run at all. Need  to devise a means of
skipping X tests if a template has an error or skips any
tests. Perhaps C<run_test_tmpl> should be considered a test
in and of itself whose result is posted after all other
processing?

=head1 AUTHOR & COPYRIGHT

MT::Test is Copyright 2006-2007, Timothy Appnel, Appnel
Internet Solutions, LLC. http://appnel.com/

This program is free software; you can use, modify, and
redistribute it under the same terms as Perl 5.8.x itself.

See http://www.perl.com/perl/misc/Artistic.html

=cut
